/*
 * timer.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version timer.c 1.0.0
 * @package libbspcore
 * @author Dr.NP <np@bsgroup.org>
 * @update 03/21/2011
 */

/**
 * Multi timer operator
 *
 * === CHANGELOG ===
 * [03/21/2011] - Creation
 */

#include "bsp.h"

#include "bsp_timer.h"
#include "bsp_thread.h"
#include "bsp_utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <err.h>
#include <sys/time.h>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#include <pthread.h>

struct timer_t **timer_list = NULL;
size_t timer_list_size;
int timer_total;

struct timer_t **free_timer_list = NULL;
size_t free_timer_list_size;
int free_timer_total;

pthread_mutex_t timer_list_lock = PTHREAD_MUTEX_INITIALIZER;

int do_timer(int);
int del_timer(int);

int tiner_init()
{
    timer_list_size = 
        free_timer_list_size = 
        timer_total = 
        free_timer_total = 0;
    
    timer_list = ensure_list_space(NULL, sizeof(struct timer_t *), &timer_list_size, TIMER_LIST_INITIAL);
    free_timer_list = ensure_list_space(NULL, sizeof(struct timer_t *), &free_timer_list_size, TIMER_LIST_INITIAL);

    return RTN_SUCCESS;
}

int new_timer(time_t it_sec, 
              long it_nsec, 
              u_int64_t loop, 
              void (* cb) (int, void *), 
              void *arg)
{
    struct timer_t *t;
    struct itimerspec new;

    if (free_timer_total > 0)
    {
        t = free_timer_list[-- free_timer_total];
    }

    else
    {
        t = malloc(sizeof(struct timer_t));
        if (!t)
        {
            return RTN_ERROR_MEMORY_ALLOC;
        }

        memset(t, 0, sizeof(struct timer_t));
        
        while (timer_total >= timer_list_size)
        {
            timer_list = ensure_list_space(timer_list, sizeof(struct timer_t *), &timer_list_size, timer_list_size * 2);
            if (!timer_list)
            {
                err(RTN_ERROR_MEMORY_ALLOC, "Timer list error!!!");
            }
        }

        timer_list[timer_total] = t;
        t->id = timer_total;
    }

    int fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK);

    if (fd < 0)
    {
        timer_list[t->id] = NULL;
        free(t);
        
        return RTN_ERROR_IO_TIMER;
    }

    set_fd(fd, t->id, FD_TYPE_TIMER);

    new.it_value.tv_sec = it_sec;
    new.it_value.tv_nsec = it_nsec;
    new.it_interval.tv_sec = it_sec;
    new.it_interval.tv_nsec = it_nsec;

    if (timerfd_settime(fd, 0, &new, NULL) < 0)
    {
        timer_list[t->id] = NULL;
        free(t);
        
        return RTN_ERROR_FATAL;
    }

    t->fd = fd;
    t->interval.tv_sec = it_sec;
    t->interval.tv_nsec = it_nsec;
    t->cb = cb;
    t->arg = arg;
    t->loop = loop;
    t->ev.events = EPOLLIN;
    t->ev.data.fd = fd;
    t->worker_id = 0;

    timer_total ++;

    dispatch_timer(t->id);

    return t->id;
}

int do_timer(int timer_id)
{
    int need_stop = 0;
    struct timer_t *t = get_timer(timer_id);

    if (!t)
    {
        return RTN_ERROR_GENERAL;
    }

    if (t->loop == 1)
    {
        need_stop = 1;
    }

    t->loop --;
    if (t->cb)
    {
        t->cb(t->id, t->arg);
    }

    if (need_stop)
    {
        del_timer(timer_id);
    }

    return RTN_SUCCESS;
}

int del_timer(int timer_id)
{
    struct itimerspec new;
    struct timer_t *t = get_timer(timer_id);

    if (!t)
    {
        return RTN_ERROR_GENERAL;
    }

    // Stop the timer
    new.it_value.tv_sec = 0;
    new.it_value.tv_nsec = 0;
    new.it_interval.tv_sec = 0;
    new.it_interval.tv_nsec = 0;

    timerfd_settime(t->fd, 0, &new, NULL);

    while (free_timer_total >= free_timer_list_size)
    {
        free_timer_list = ensure_list_space(free_timer_list, sizeof(struct timer_t *), &free_timer_list_size, free_timer_list_size * 2);
        if (!free_timer_list)
        {
            err(RTN_ERROR_MEMORY_ALLOC, "Free timer list error!!!");
        }
    }

    remove_from_worker(t->fd, t->worker_id);
    free_timer_list[free_timer_total ++] = t;
    
    return RTN_SUCCESS;
}

struct timer_t * get_timer(int timer_id)
{
    struct timer_t *t = NULL;

    if (timer_id >= 0 && timer_id < timer_total)
    {
        t = timer_list[timer_id];
    }

    return t;
}
